#ifndef ENUM_DEF_HPP
#define ENUM_DEF_HPP

#if __cplusplus > 201700L
#include <type_traits>
#include <string>
#include <algorithm>
#include <iostream>

using std::false_type;
using std::true_type;
using std::conditional_t;

#define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2) arg1##arg2

#define FOR_EACH_NARG(...) FOR_EACH_NARG_(1, ##__VA_ARGS__ , FOR_EACH_RSEQ_N())
#define FOR_EACH_NARG_(...) FOR_EACH_ARG_N(__VA_ARGS__)
#define FOR_EACH_ARG_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define FOR_EACH_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0

#define REPEAT_0(...) \

/* none变量禁止最后一层宏展开 */

#define REPEAT_1(what, T, none, name, ...) \
what(T, name ## none) \

#define REPEAT_2(what, T, none, name, ...) \
what(T, name ## none) \
REPEAT_1(what, T, none, ##__VA_ARGS__)

#define REPEAT_3(what, T, none, name, ...) \
what(T, name ## none) \
REPEAT_2(what, T, none, ##__VA_ARGS__)

#define REPEAT_4(what, T, none, name, ...) \
what(T, name ## none) \
REPEAT_3(what, T, none, ##__VA_ARGS__)

#define REPEAT_5(what, T, none, name, ...) \
what(T, name ## none) \
REPEAT_4(what, T, none, ##__VA_ARGS__)

#define REPEAT_6(what, T, none, name, ...) \
what(T, name ## none) \
REPEAT_5(what, T, none, ##__VA_ARGS__)

#define REPEAT_7(what, T, none, name, ...) \
what(T, name ## none) \
REPEAT_6(what, T, none, ##__VA_ARGS__)

#define REPEAT_8(what, T, none, name, ...) \
what(T, name ## none) \
REPEAT_7(what, T, none, ##__VA_ARGS__)

#define FOR_EACH_(N, what, T, none, ...) \
CONCATENATE(REPEAT_, N) (what, T, none, ##__VA_ARGS__)

#define FOR_EACH(what, T, ...) \
FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, T, , ##__VA_ARGS__)

#define GENERATOR_ORIGIN_NAME(T, name) \
/* origin_name full */ \
template <> \
struct T::origin_name<name> { \
    using type = void; \
    constexpr static const char* value = #name; \
}; \

template <class T>
struct option_t {
template <T... Vals>
struct option_list {};
};

template <class T>
template <T N, T... Vals>
struct option_t<T>::option_list<N, Vals...> : option_list<Vals...> {
};

#define DECLARE_OPTION_BLOCK(T, name, ...) \
struct name { \
using type = T; \
using all_options = option_t<type>::option_list<__VA_ARGS__>; \
\
/* origin_name primary */ \
template <type option> \
struct origin_name { \
    constexpr static const char* const value = "unknown"; \
}; \
\
/* new_name primary */ \
template <type option> \
struct new_name { \
}; \
\
template <type option, class TMP = void> \
struct has_new_name : public false_type {}; \
\
template <type option> \
struct has_new_name<option, typename new_name<option>::type> : public true_type {}; \
\
template <type option> \
constexpr static bool has_new_name_v = has_new_name<option>::value; \
\
template <type option, class TMP = void> \
struct has_origin_name : public false_type {}; \
\
template <type option> \
struct has_origin_name<option, typename origin_name<option>::type> : public true_type {}; \
\
template <type option> \
constexpr static bool has_origin_name_v = has_origin_name<option>::value; \
\
struct unknown { \
    constexpr static const char* const value = "unknown"; \
}; \
\
template <type option> \
constexpr static std::string option_name() { \
    return std::conditional_t<   has_new_name_v<option>,  \
                                new_name<option>,  \
                                std::conditional_t<has_origin_name_v<option>,  \
                                    origin_name<option>,  \
                                    unknown>>::value; \
} \
template <type arg, type... options, class Fun, class Tar> \
constexpr static std::string find_option_name(Tar x, Fun&& fun) { \
    if (fun(x, arg)) { \
        return option_name<arg>(); \
    } \
    if constexpr (sizeof...(options) == 0) { \
        return ""; \
    } else { \
        return find_option_name<options...>(x, std::forward<Fun>(fun)); \
    } \
} \
\
template <type arg, type... options, class Tar> \
constexpr static std::string find_option_name(Tar x);  \
\
template <class Tar> \
constexpr static std::string find_option_name(Tar x); \
template <type arg, type... options, class Fun, class Tar> \
constexpr static std::string find_multi_option_name(Tar x, const std::string& sep, Fun&& fun) { \
    std::string res; \
    if (fun(x, arg)) { \
        res = option_name<arg>(); \
    } \
    if constexpr (sizeof...(options) == 0) { \
        return res; \
    } else { \
        std::string next = find_multi_option_name<options...>(x, sep, std::forward<Fun>(fun)); \
        if (res.empty() || next.empty()) { \
            return res + next; \
        } \
        return res + sep + next; \
    } \
} \
\
template <type arg, type... options, class Tar> \
constexpr static std::string find_multi_option_name(Tar x, const std::string& sep); \
\
template <type arg, type... options, class Tar> \
constexpr static std::string find_multi_option_name(Tar x); \
\
template <class Tar> \
constexpr static std::string find_multi_option_name(Tar x, const std::string& sep); \
\
template <class Tar> \
constexpr static std::string find_multi_option_name(Tar x); \
\
private: \
template <class Tar, class Fun, type... options> \
constexpr static std::string find_option_name_impl(Tar x, Fun&& fun, option_t<type>::option_list<options...>) { \
    return find_option_name<options...>(x, std::forward<Fun>(fun)); \
} \
template <class Tar, class Fun, type... options> \
constexpr static std::string find_multi_option_name_impl(Tar x, const std::string& sep, Fun&& fun, option_t<type>::option_list<options...>) { \
    return find_multi_option_name<options...>(x, sep, std::forward<Fun>(fun)); \
} \
}; \
FOR_EACH(GENERATOR_ORIGIN_NAME, name, ##__VA_ARGS__) \


#define DECLARE_OPTION_NAME(T, x, name) \
template <> \
struct T::new_name<x> { \
    using type = void; \
    constexpr static const char* const value = name; \
}; \

#define DECLARE_FIND_OPTION_NAME(T, fun) \
template <T::type arg, T::type... options, class Tar> \
constexpr std::string T::find_option_name(Tar x) { \
    return find_option_name<arg, options...>(x, fun); \
} \
template <class Tar> \
constexpr std::string T::find_option_name(Tar x) { \
    return find_option_name_impl(x, fun, T::all_options{}); \
} \

#define DECLARE_FIND_OPTION_NAME_DEFAULT(T) \
/* default_version condition:equal */ \
template <T::type arg, T::type... options, class Tar> \
constexpr std::string T::find_option_name(Tar x) { \
    return find_option_name<arg, options...>(x, [](auto&& target, const auto& option){ return target == static_cast<Tar>(option); }); \
} \
template <class Tar> \
constexpr std::string T::find_option_name(Tar x) { \
    return find_option_name_impl(x, [](auto&& target, const auto& option){ return target == static_cast<Tar>(option); }, T::all_options{}); \
} \

#define DECLARE_FIND_MULTI_OPTION_NAME(T, sep_str, fun) \
template <T::type arg, T::type... options, class Tar> \
constexpr std::string T::find_multi_option_name(Tar x, const std::string& sep) { \
    return find_multi_option_name<arg, options...>(x, sep, fun); \
} \
template <T::type arg, T::type... options, class Tar> \
constexpr std::string T::find_multi_option_name(Tar x) { \
    return find_multi_option_name<arg, options...>(x, std::string(sep_str), fun); \
} \
template <class Tar> \
constexpr std::string T::find_multi_option_name(Tar x, const std::string& sep) { \
    return find_multi_option_name_impl(x, sep, fun, T::all_options{}); \
} \
template <class Tar> \
constexpr std::string T::find_multi_option_name(Tar x) { \
    return find_multi_option_name_impl(x, std::string(sep_str), fun, T::all_options{}); \
} \

#define DECLARE_FIND_MULTI_OPTION_NAME_DEFAULT(T) \
/* default 2 */ \
template <T::type arg, T::type... options, class Tar> \
constexpr std::string T::find_multi_option_name(Tar x, const std::string& sep) { \
    return find_multi_option_name<arg, options...>(x, sep, [](auto&& target, const auto& option){ return target & static_cast<Tar>(option); }); \
} \
/* default 1 */ \
template <T::type arg, T::type... options, class Tar> \
constexpr std::string T::find_multi_option_name(Tar x) { \
    return find_multi_option_name<arg, options...>(x, std::string("|"), [](auto&& target, const auto& option){ return target & static_cast<Tar>(option); }); \
} \
/* default 2 */ \
template <class Tar> \
constexpr std::string T::find_multi_option_name(Tar x, const std::string& sep) { \
    return find_multi_option_name_impl(x, sep, [](auto&& target, const auto& option){ return target & static_cast<Tar>(option); }, T::all_options{}); \
} \
/* default 1 */ \
template <class Tar> \
constexpr std::string T::find_multi_option_name(Tar x) { \
    return find_multi_option_name_impl(x, std::string("|"), [](auto&& target, const auto& option){ return target & static_cast<Tar>(option); }, T::all_options{}); \
} \

#else /* __cplusplus > 201700L */

#define DECLARE_OPTION_BLOCK(...)
#define DECLARE_FIND_MULTI_OPTION_NAME(...)
#define DECLARE_FIND_MULTI_OPTION_NAME_DEFAULT(...)
#define DECLARE_FIND_OPTION_NAME(...)
#define DECLARE_FIND_OPTION_NAME_DEFAULT(...)
#define DECLARE_OPTION_NAME(...)

#endif /* other */

template <class T>
constexpr auto get_class_name() {
    constexpr std::string_view func_name = __PRETTY_FUNCTION__;
    constexpr auto begin = func_name.find_first_of('=') + 2;
    constexpr auto end = func_name.find_first_of(']');
    return func_name.substr(begin, end - begin);
}

template <class T>
constexpr auto get_class_name(T) {
    return get_class_name<T>();
}

#endif